import importlib
import json
import traceback
from datetime import datetime
from inspect import getfile
from pathlib import Path
from threading import Thread

from vnsumscope.app.cta_strategy import CtaTemplate
from vnsumscope.app.cta_strategy.backtesting import (
    BacktestingEngine, OptimizationSetting, BacktestingMode
)
from vnsumscope.database.sqlite import records_manager
from vnsumscope.event import Event, EventEngine
from vnsumscope.trader.constant import Interval
from vnsumscope.trader.database import database_manager
from vnsumscope.trader.engine import BaseEngine, MainEngine
from vnsumscope.trader.object import HistoryRequest
from vnsumscope.trader.rqdata import rqdata_client
from vnsumscope.trader.utility import extract_vt_symbol

APP_NAME = "CtaBacktester"

EVENT_BACKTESTER_LOG = "eBacktesterLog"
EVENT_BACKTESTER_BACKTESTING_FINISHED = "eBacktesterBacktestingFinished"
EVENT_BACKTESTER_BACKTESTING_ERROR = "eBacktesterBacktestingError"
EVENT_BACKTESTER_OPTIMIZATION_FINISHED = "eBacktesterOptimizationFinished"
EVENT_BACKTESTER_OPTIMIZATION_ERROR = "eBacktesterOptimizationError"


class BacktesterEngine(BaseEngine):
    """
    For running CTA strategy backtesting.
    """

    def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
        """"""
        super().__init__(main_engine, event_engine, APP_NAME)

        self.classes = {}
        self.backtesting_engine = None
        self.thread = None

        # Backtesting reuslt
        self.result_df = None
        self.result_statistics = None
        self.record_trade = None
        self.record_order = None

        # Optimization result
        self.result_values = None

    def init_engine(self, strategy_path):
        """"""
        self.write_log("初始化CTA回测引擎")

        self.backtesting_engine = BacktestingEngine()
        # Redirect log from backtesting engine outside.
        self.backtesting_engine.output = self.write_log

        self._strategy_path = strategy_path
        self.load_strategy_class()
        self.write_log("策略文件加载完成")

        self.init_rqdata()

    def init_rqdata(self):
        """
        Init RQData client.
        """
        result = rqdata_client.init()
        if result:
            self.write_log("RQData数据接口初始化成功")

    def write_log(self, msg: str):
        """"""
        event = Event(EVENT_BACKTESTER_LOG)
        event.data = msg
        self.event_engine.put(event)

    def load_strategy_class(self):
        """
        Load strategy class from source code.
        """
        strategy_file_name = Path(self._strategy_path).stem
        if 'app' in self._strategy_path:
            module_name = f'vnsumscope.app.cta_strategy.strategies.{strategy_file_name}'
        else:
            from vnsumscope.trader.utility import CUSTOM_CTA_MODULE
            module_name = f'{CUSTOM_CTA_MODULE}.{strategy_file_name}'
        try:
            module = importlib.import_module(module_name)
            importlib.reload(module)
            for name in dir(module):
                value = getattr(module, name)
                if (isinstance(value, type) and issubclass(value, CtaTemplate) and value is not CtaTemplate):
                    self.classes[value.__name__] = value
        except:  # noqa
            msg = f"策略文件{self._strategy_path}加载失败，触发异常：\n{traceback.format_exc()}"
            self.write_log(msg)

    def reload_strategy_class(self):
        """"""
        self.classes.clear()
        self.load_strategy_class()
        self.write_log("策略文件重载刷新完成")

    def get_strategy_class_names(self):
        """"""
        return list(self.classes.keys())

    def run_backtesting(
            self,
            class_name: str,
            vt_symbol: str,
            interval: str,
            start: datetime,
            end: datetime,
            rate: float,
            slippage: float,
            size: int,
            pricetick: float,
            capital: int,
            inverse: bool,
            setting: dict
    ):
        """"""
        self.result_df = None
        self.result_statistics = None


        engine = self.backtesting_engine
        engine.clear_data()

        if interval == Interval.TICK.value:
            mode = BacktestingMode.TICK
        else:
            mode = BacktestingMode.BAR

        engine.set_parameters(
            vt_symbol=vt_symbol,
            interval=interval,
            start=start,
            end=end,
            rate=rate,
            slippage=slippage,
            size=size,
            pricetick=pricetick,
            capital=capital,
            inverse=inverse,
            mode=mode
        )

        strategy_class = self.classes[class_name]

        try:
            engine.add_strategy(
                strategy_class,
                setting
            )
            parameters = {
                'vt_symbol': vt_symbol,
                'interval': interval,
                'start': start,
                'end': end,
                'rate': rate,
                'slippage': slippage,
                'size': size,
                'pricetick': pricetick,
                'capital': capital,
                'mode': mode,
                'params': json.dumps(setting)
            }
            record = records_manager.create_record(
                strategy_path=self._strategy_path,
                params=parameters
            )
            engine.load_data()
            engine.run_backtesting()
            self.result_df = engine.calculate_result()
            self.result_statistics = engine.calculate_statistics(output=False)
            self.record_trade = list(engine.trades.values())
            self.record_order = list(engine.limit_orders.values())
        except Exception:
            msg = f"策略回测失败，触发异常：\n{traceback.format_exc()}"
            self.write_log(msg)
            event = Event(EVENT_BACKTESTER_BACKTESTING_ERROR)
            data = {'status': 'failed', 'total_return': None}
        else:
            msg = "策略回测完成"
            self.write_log(msg)
            event = Event(EVENT_BACKTESTER_BACKTESTING_FINISHED)
            data = {'status': 'success', 'total_return': self.result_statistics['total_return']}
            records_manager.save_statistic(record.id, self.result_statistics)
            records_manager.save_trade(record.id, self.record_trade)
            records_manager.save_order(record.id, self.record_order)
            records_manager.save_daily_result(record.id, self.result_df, 'cta')
        finally:
            records_manager.update_record(record_id=record.id, data=data)
            self.event_engine.put(event)
            self.thread = None

    def start_backtesting(
            self,
            class_name: str,
            vt_symbol: str,
            interval: str,
            start: datetime,
            end: datetime,
            rate: float,
            slippage: float,
            size: int,
            pricetick: float,
            capital: int,
            inverse: bool,
            setting: dict
    ):
        if self.thread:
            self.write_log("已有任务在运行中，请等待完成")
            return False

        self.write_log("-" * 40)
        self.thread = Thread(
            target=self.run_backtesting,
            args=(
                class_name,
                vt_symbol,
                interval,
                start,
                end,
                rate,
                slippage,
                size,
                pricetick,
                capital,
                inverse,
                setting
            )
        )
        self.thread.start()

        return True

    def get_result_df(self):
        """"""
        return self.result_df

    def get_result_statistics(self):
        """"""
        return self.result_statistics

    def get_result_values(self):
        """"""
        return self.result_values

    def get_default_setting(self, class_name: str):
        """"""
        strategy_class = self.classes[class_name]
        return strategy_class.get_class_parameters()

    def run_optimization(
            self,
            class_name: str,
            vt_symbol: str,
            interval: str,
            start: datetime,
            end: datetime,
            rate: float,
            slippage: float,
            size: int,
            pricetick: float,
            capital: int,
            inverse: bool,
            optimization_setting: OptimizationSetting,
            use_ga: bool
    ):
        """"""
        if use_ga:
            self.write_log("开始遗传算法参数优化")
        else:
            self.write_log("开始多进程参数优化")

        self.result_values = None

        engine = self.backtesting_engine
        engine.clear_data()

        if interval == Interval.TICK:
            mode = BacktestingMode.TICK
        else:
            mode = BacktestingMode.BAR

        engine.set_parameters(
            vt_symbol=vt_symbol,
            interval=interval,
            start=start,
            end=end,
            rate=rate,
            slippage=slippage,
            size=size,
            pricetick=pricetick,
            capital=capital,
            inverse=inverse,
            mode=mode
        )
        try:
            strategy_class = self.classes[class_name]
            engine.add_strategy(
                strategy_class,
                {}
            )

            if use_ga:
                self.result_values = engine.run_ga_optimization(
                    optimization_setting,
                    output=False
                )
            else:
                self.result_values = engine.run_optimization(
                    optimization_setting,
                    output=False
                )
        except Exception:
            self.write_log("多进程参数优化失败")
            msg = f"策略回测失败，触发异常：\n{traceback.format_exc()}"
            self.write_log(msg)
            event = Event(EVENT_BACKTESTER_OPTIMIZATION_ERROR)
        else:
            self.write_log("多进程参数优化完成")
            event = Event(EVENT_BACKTESTER_OPTIMIZATION_FINISHED)
        finally:
            self.event_engine.put(event)
            self.thread = None

    def start_optimization(
            self,
            class_name: str,
            vt_symbol: str,
            interval: str,
            start: datetime,
            end: datetime,
            rate: float,
            slippage: float,
            size: int,
            pricetick: float,
            capital: int,
            inverse: bool,
            optimization_setting: OptimizationSetting,
            use_ga: bool
    ):
        if self.thread:
            self.write_log("已有任务在运行中，请等待完成")
            return False

        self.write_log("-" * 40)
        self.thread = Thread(
            target=self.run_optimization,
            args=(
                class_name,
                vt_symbol,
                interval,
                start,
                end,
                rate,
                slippage,
                size,
                pricetick,
                capital,
                inverse,
                optimization_setting,
                use_ga
            )
        )
        self.thread.start()

        return True

    def run_downloading(
            self,
            vt_symbol: str,
            interval: str,
            start: datetime,
            end: datetime
    ):
        """
        Query bar data from RQData.
        """
        self.write_log(f"{vt_symbol}-{interval}开始下载历史数据")

        try:
            symbol, exchange = extract_vt_symbol(vt_symbol)
        except ValueError:
            self.write_log(f"{vt_symbol}解析失败，请检查交易所后缀")
            self.thread = None
            return

        req = HistoryRequest(
            symbol=symbol,
            exchange=exchange,
            interval=Interval(interval),
            start=start,
            end=end
        )

        contract = self.main_engine.get_contract(vt_symbol)

        try:
            # If history data provided in gateway, then query
            if contract and contract.history_data:
                data = self.main_engine.query_history(
                    req, contract.gateway_name
                )
            # Otherwise use RQData to query data
            else:
                data = rqdata_client.query_history(req)

            if data:
                database_manager.save_bar_data(data)
                self.write_log(f"{vt_symbol}-{interval}历史数据下载完成")
            else:
                self.write_log(f"数据下载失败，无法获取{vt_symbol}的历史数据")
        except Exception:
            msg = f"数据下载失败，触发异常：\n{traceback.format_exc()}"
            self.write_log(msg)

        # Clear thread object handler.
        self.thread = None

    def start_downloading(
            self,
            vt_symbol: str,
            interval: str,
            start: datetime,
            end: datetime
    ):
        if self.thread:
            self.write_log("已有任务在运行中，请等待完成")
            return False

        self.write_log("-" * 40)
        self.thread = Thread(
            target=self.run_downloading,
            args=(
                vt_symbol,
                interval,
                start,
                end
            )
        )
        self.thread.start()

        return True

    def get_all_trades(self):
        """"""
        return self.backtesting_engine.get_all_trades()

    def get_all_orders(self):
        """"""
        return self.backtesting_engine.get_all_orders()

    def get_all_daily_results(self):
        """"""
        return self.backtesting_engine.get_all_daily_results()

    def get_history_data(self):
        """"""
        return self.backtesting_engine.history_data

    def get_strategy_class_file(self, class_name: str):
        """"""
        strategy_class = self.classes[class_name]
        file_path = getfile(strategy_class)
        return file_path
